
#include "wave/riff_wave_writer.h"
#include "logger.h"

#include <iostream>
#include <sstream>

// only for testmethode flipSecondChannel()
#define SAMPLE_BLOCK_SIZE 2048

using namespace std;

CRiffWaveWriter::CRiffWaveWriter()
		:mWav(),
		mRiffChunksize(0),
		mFormat(),
		mData()
{
}

CRiffWaveWriter::~CRiffWaveWriter()
{
	close();
}

bool CRiffWaveWriter::create
(
		std::string pFilename,
		NWaveFormatTag::EFormatTag pFormatTag,
		uint16_t pChannels,
		uint32_t pSamplesPerSecond,
		uint16_t pBitsPerSample,
		bool pOverwrite
)
{
	if(mWav.is_open()) {
		return false;
	}
	if(!pOverwrite) {
		// check if file doesn't exist
		// alternative: with stat function
		std::ifstream lWav;
		lWav.open(pFilename.c_str(), fstream::in | fstream::binary);
		if(!lWav.fail()) {
			printl(LL_ERR, __FILE__, "File already exist.\n");
			lWav.close();
			return false;
		}
	}
	if(pChannels == 0) {
		printl(LL_ERR, __FILE__, "Can't create a wave file with 0 channels.\n");
		return false;
	}
	if(pSamplesPerSecond == 0) {
		printl(LL_ERR, __FILE__, "Can't create a wave file with 0 samples per second.\n");
		return false;
	}
	if(pBitsPerSample == 0) {
		printl(LL_ERR, __FILE__, "Can't create a wave file with 0 bits per sample.\n");
		return false;
	}

	mData.bytePerSample = (pBitsPerSample + 7) / 8;

	mFormat.formatTag = pFormatTag;
	mFormat.channels = pChannels;
	mFormat.samplesPerSec = pSamplesPerSecond; // samples per sec (per channel) = frames per sec
	mFormat.blockAlign = mData.bytePerSample * pChannels; // = size of one frame
	mFormat.avgBytesPerSec = pSamplesPerSecond * mFormat.blockAlign;
	mFormat.bitsPerSample = pBitsPerSample;

	mWav.open(pFilename.c_str(), fstream::out | fstream::binary | fstream::trunc);
	if(mWav.fail()) {
		printl(LL_ERR, __FILE__, "Creation failed.\n");
		return false;
	}
	if(!writeChunks()) {
		close();
		return false;
	}
	return true;
}

bool CRiffWaveWriter::close()
{
	if(!mWav.is_open())
		return false;
	sync();
	cout << getWaveInfo() << endl;
	mWav.close();
	if(mWav.fail())
		return false;
	return true;
}

bool CRiffWaveWriter::sync()
{
	if(!mWav.is_open())
		return false;

	uint32_t lChunkSize(0);
	std::ofstream::pos_type currentFilePos(mWav.tellp());
	if(mWav.fail())
		return false;

	// === chunk size for RIFF-Chunk ===
	if(currentFilePos < 8) {
		return false;
	}
	lChunkSize = (uint32_t)currentFilePos - 8;
	if(lChunkSize != mRiffChunksize) {
		cout << "RIFF chunksize: old: " << mRiffChunksize
				<< ", new: " << lChunkSize << endl;
		mWav.seekp(4, ios_base::beg);
		if(mWav.fail()) {
			mWav.seekp(currentFilePos, ios_base::beg);
			return false;
		}
		// chunk size (4) from RIFF-Chunk: filesize in bytes - 8
		mWav.write(reinterpret_cast<char *>(&lChunkSize), 4);
		if(mWav.fail()) {
			mWav.seekp(currentFilePos, ios_base::beg);
			return false;
		}
		mRiffChunksize = lChunkSize;
	}
	else {
		cout << "RIFF chunksize: unchanged: " << mRiffChunksize << endl;
	}

	// === chunk size for data-Chunk ===
	if(currentFilePos < mData.sampleStartPos) {
		mWav.seekp(currentFilePos, ios_base::beg);
		return false;
	}
	lChunkSize = currentFilePos - mData.sampleStartPos;
	if(lChunkSize != mData.dataChunksize) {
		cout << "data chunksize: old: " << mData.dataChunksize
				<< ", new: " << lChunkSize << endl;
		mWav.seekp((long)mData.sampleStartPos - 4, ios_base::beg);
		if(mWav.fail()) {
			mWav.seekp(currentFilePos, ios_base::beg);
			return false;
		}
		// chunk size (4) from data-Chunk
		mWav.write(reinterpret_cast<char *>(&lChunkSize), 4);
		if(mWav.fail()) {
			mWav.seekp(currentFilePos, ios_base::beg);
			return false;
		}

		mData.dataChunksize = lChunkSize;
	}
	else {
		cout << "data chunksize: unchanged: " << mData.dataChunksize << endl;
	}

	// set original file position
	mWav.seekp(currentFilePos, ios_base::beg);
	if(mWav.fail()) {
		return false;
	}
	return true;
}

string CRiffWaveWriter::getWaveInfo()
{
	stringstream s;
	s << endl;
	s << "formatTag: " << mFormat.formatTag << endl;
	s << "channels: " << mFormat.channels << endl;
	s << "samples per second (per channel): " << mFormat.samplesPerSec << endl;
	s << "bytes per second: " << mFormat.avgBytesPerSec << endl;
	s << "block align (byte per frame): " << mFormat.blockAlign << endl;
	s << "bits per sample: " << mFormat.bitsPerSample << endl;
	s << endl;
	s << "byte per sample: " << bytePerSample() << endl;
	s << "size of samples (bytes): " << mData.dataChunksize << endl;
	s << "samples per channel (=frame count): " << (mData.dataChunksize / mFormat.blockAlign) << endl;
	s << "samples from all channels: " << sampleCount() << endl;
	s << "seconds: "
			<< (static_cast<float>(mData.dataChunksize)
					/ static_cast<float>(mFormat.avgBytesPerSec))
			<< endl;
	return s.str();
}

unsigned int CRiffWaveWriter::sampleCount()
{
	return mData.dataChunksize / mData.bytePerSample;
}

unsigned int CRiffWaveWriter::channels()
{
	return mFormat.channels;
}

unsigned int CRiffWaveWriter::bytePerSample()
{
	return mData.bytePerSample;
}

unsigned int CRiffWaveWriter::writeSamples
(
		void* pSamples,
		unsigned int pCount
)
{
	if(!mWav.is_open())
		return false;

	while(pCount % mFormat.channels) {
		cout << "warning: pCount should be a multiple of channels, discard last sample" << endl;
		pCount--;
		if(pCount == 0)
			return 0;
	}
	if(pCount == 0) {
		return 0;
	}
	mWav.write(reinterpret_cast<char *>(pSamples), pCount * mData.bytePerSample);
	if(mWav.fail())
		return 0;
	//cout << "count: "<< pCount << ", bytes: " << pCount * mData.bytePerSample << endl;
	return pCount;
}

bool CRiffWaveWriter::writeChunks()
{
	if(!mWav.is_open()) {
		return false;
	}

	// === RIFF-Chunk ===
	// chunk ID (4): "RIFF"
	mWav.write("RIFF", 4);
	if(mWav.fail())
		return false;

	// chunk size (4): filesize in bytes - 8
	// currently write 0 as chunksize but will be set correcty with sync()
	mRiffChunksize = 0;
	mWav.write(reinterpret_cast<char *>(&mRiffChunksize), 4);
	if(mWav.fail())
		return false;

	// riff type (4): "WAVE"
	mWav.write("WAVE", 4);
	if(mWav.fail())
		return false;
	
	// === Format-Chunk ===
	// chunk ID (4): "fmt "
	mWav.write("fmt ", 4);
	if(mWav.fail())
		return false;

	// chunk size (4): content of format chunk in bytes without chunk id and chunk size
	uint32_t lChunkSize(16);
	mWav.write(reinterpret_cast<char *>(&lChunkSize), 4);
	if(mWav.fail())
		return false;

	mWav.write(reinterpret_cast<char *>(&mFormat.formatTag), 2);
	if(mWav.fail())
		return false;
	mWav.write(reinterpret_cast<char *>(&mFormat.channels), 2);
	if(mWav.fail())
		return false;
	mWav.write(reinterpret_cast<char *>(&mFormat.samplesPerSec), 4);
	if(mWav.fail())
		return false;
	mWav.write(reinterpret_cast<char *>(&mFormat.avgBytesPerSec), 4);
	if(mWav.fail())
		return false;
	mWav.write(reinterpret_cast<char *>(&mFormat.blockAlign), 2);
	if(mWav.fail())
		return false;
	mWav.write(reinterpret_cast<char *>(&mFormat.bitsPerSample), 2);
	if(mWav.fail())
		return false;

	// === Data-Chunk ===
	// chunk ID (4): "data"
	mWav.write("data", 4);
	if(mWav.fail())
		return false;

	// chunk size (4): content of data chunk in bytes without chunk id and chunk size
	// chunk size = framecount * blockalign
	mData.dataChunksize = 0;
	mWav.write(reinterpret_cast<char *>(&mData.dataChunksize), 4);
	if(mWav.fail())
		return false;

	mData.sampleStartPos = mWav.tellp();
	if(mWav.fail())
		return false;

	if(!sync()) {
		cout << "sync failed" << endl;
		return false;
	}
	if(!sync()) {
		cout << "sync failed" << endl;
		return false;
	}
	return true;
}

